/**************************************************************************
 *                                                                        *
 *         Copyright (c) 2014 by Generalplus Inc.                         *
 *                                                                        *
 *  This software is copyrighted by and is the property of Generalplus    *
 *  Inc. All rights are reserved by Generalplus Inc.                      *
 *  This software may only be used in accordance with the                 *
 *  corresponding license agreement. Any unauthorized use, duplication,   *
 *  distribution, or disclosure of this software is expressly forbidden.  *
 *                                                                        *
 *  This Copyright notice MUST not be removed or modified without prior   *
 *  written consent of Generalplus Technology Co., Ltd.                   *
 *                                                                        *
 *  Generalplus Inc. reserves the right to modify this software           *
 *  without notice.                                                       *
 *                                                                        *
 *  Generalplus Inc.                                                      *
 *  No.19, Industry E. Rd. IV, Hsinchu Science Park                       *
 *  Hsinchu City 30078, Taiwan, R.O.C.                                    *
 *                                                                        *
 **************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "driver_l2.h"

#if (defined _DRV_L2_SCCB) && (_DRV_L2_SCCB == 1)

/**************************************************************************
 *                           C O N S T A N T S                            *
 **************************************************************************/

/**************************************************************************
 *                          D A T A    T Y P E S                          *
 **************************************************************************/

/**************************************************************************
 *                         G L O B A L    D A T A                         *
 **************************************************************************/
static void sccb_delay(INT32U i)
{
	drv_tiny_wait(i * 23);
}

static void sccb_lock(sccb_config_t *pSccb)
{
}

static void sccb_unlock(sccb_config_t *pSccb)
{
}

static void sccb_start(sccb_config_t *pSccb)
{
	gpio_write_io(pSccb->scl_port, DATA_HIGH);
	gpio_write_io(pSccb->sda_port, DATA_HIGH);
	sccb_delay(1);
	gpio_write_io(pSccb->sda_port, DATA_LOW);
	sccb_delay(1);
	gpio_write_io(pSccb->scl_port, DATA_LOW);
	sccb_delay(1);
}

static void sccb_stop(sccb_config_t *pSccb)
{
	gpio_write_io(pSccb->scl_port, DATA_LOW);
	gpio_write_io(pSccb->sda_port, DATA_LOW);
	sccb_delay(1);
	gpio_write_io(pSccb->scl_port, DATA_HIGH);
	sccb_delay(1);
	gpio_write_io(pSccb->sda_port, DATA_HIGH);
	sccb_delay(1);
}

static INT32S sccb_w_phase(sccb_config_t *pSccb, INT16U value, INT8U ack)
{
	INT32U	i;
	INT32S	ret = STATUS_OK;

	for(i = 0; i < 8; i++)
	{
		gpio_write_io(pSccb->scl_port, DATA_LOW);
		if(value & 0x80)
		{
			gpio_write_io(pSccb->sda_port, DATA_HIGH);
		}
		else
		{
			gpio_write_io(pSccb->sda_port, DATA_LOW);
		}

		sccb_delay(1);
		gpio_write_io(pSccb->scl_port, DATA_HIGH);
		sccb_delay(1);
		value <<= 1;
	}

	// The 9th bit transmission
	gpio_write_io(pSccb->scl_port, DATA_LOW);
	gpio_init_io(pSccb->sda_port, GPIO_FLOAT);		//SDA is Hi-Z mode
	sccb_delay(1);
	gpio_write_io(pSccb->scl_port, DATA_HIGH);

	// check ack = low
	if(ack)
	{
		for(i = 0; i < pSccb->timeout; i++)
		{
			if(gpio_read_io(pSccb->sda_port) == 0)
			{
				break;
			}
		}
	}

	if(i == pSccb->timeout)
	{
		ret = STATUS_FAIL;
	}

	sccb_delay(1);
	gpio_write_io(pSccb->scl_port, DATA_LOW);
	gpio_init_io(pSccb->sda_port, GPIO_OUTPUT);		//SDA is Hi-Z mode
	return ret;
}

static INT16U sccb_r_phase(sccb_config_t *pSccb, INT8U phase)
{
	INT16U	i;
	INT16U	data = 0x00;

	gpio_init_io(pSccb->sda_port, GPIO_INPUT);		//SDA is Hi-Z mode
	for(i = 0; i < 8; i++)
	{
		gpio_write_io(pSccb->scl_port, DATA_LOW);
		sccb_delay(1);
		gpio_write_io(pSccb->scl_port, DATA_HIGH);
		data <<= 1;
		data |= (gpio_read_io(pSccb->sda_port));
		sccb_delay(1);
	}

	// The 9th bit transmission
	gpio_write_io(pSccb->scl_port, DATA_LOW);
	gpio_init_io(pSccb->sda_port, GPIO_OUTPUT);		//SDA is output mode
	if(phase == 2)
	{
		gpio_write_io(pSccb->sda_port, DATA_LOW);	//SDA0, the nineth bit is ACK must be 1
	}
	else
	{
		gpio_write_io(pSccb->sda_port, DATA_HIGH);	//SDA0, the nineth bit is NAK must be 1
	}

	sccb_delay(1);
	gpio_write_io(pSccb->scl_port, DATA_HIGH);
	sccb_delay(1);
	gpio_write_io(pSccb->scl_port, DATA_LOW);
	return data;
}

static INT32S sccb_write(sccb_config_t *pData, INT16U addr, INT16U data)
{
	INT8U	temp;
	INT8U	id = pData->slave_id;
	INT8U	addr_bits = pData->addr_bits & ~0x7;
	INT8U	data_bits = pData->data_bits & ~0x7;
	INT8U	ack = 1;
	INT32S	ret;

	// 3-Phase write transmission
	// Transmission start
	sccb_start(pData);

	// Phase 1, SLAVE_ID
	ret = sccb_w_phase(pData, id, 1);
	if(ret < 0)
	{
		goto __exit;
	}

	// Phase 2, Register address
	while(addr_bits >= 8)
	{
		addr_bits -= 8;
		temp = addr >> addr_bits;
		ret = sccb_w_phase(pData, temp, ack);
		if(ret < 0)
		{
			goto __exit;
		}
	}

	// Phase 3, Register Data
	while(data_bits >= 8)
	{
		data_bits -= 8;
		if(data_bits)
		{
			ack = 1;	//ACK
		}
		else
		{
			ack = 0;	//NACK
		}

		temp = data >> data_bits;
		ret = sccb_w_phase(pData, temp, ack);
		if(ret < 0)
		{
			goto __exit;
		}
	}

__exit:
	// Transmission stop
	sccb_stop(pData);
	return ret;
}

static INT32S sccb_read(sccb_config_t *pData, INT16U addr, INT16U* data)
{
	INT8U	temp;
	INT8U	id = pData->slave_id;
	INT8U	addr_bits = pData->addr_bits & ~0x7;
	INT8U	data_bits = pData->data_bits & ~0x7;
	INT8U	ack = 1;
	INT32U	read_data;
	INT32S	ret;

	// 2-Phase write transmission cycle
	// Transmission start
	sccb_start(pData);

	// Phase 1, SLAVE_ID
	ret = sccb_w_phase(pData, id, 1);
	if(ret < 0)
	{
		goto __exit;
	}

	// Phase 2, Register Address
	while(addr_bits >= 8)
	{
		addr_bits -= 8;
		temp = addr >> addr_bits;
		ret = sccb_w_phase(pData, temp, ack);
		if(ret < 0)
		{
			goto __exit;
		}
	}

	// Transmission stop
	sccb_stop(pData);

	// 2-Phase read transmission cycle
	// Transmission start
	sccb_start(pData);

	// Phase 1 (read)
	ret = sccb_w_phase(pData, id | 0x01, 1);
	if(ret < 0)
	{
		goto __exit;
	}

	// Phase 2 (read)
	read_data = 0;
	while(data_bits >= 8)
	{
		data_bits -= 8;
		if(data_bits)
		{
			ack = 1;	//ACK
		}
		else
		{
			ack = 0;	//NACK
		}

		temp = sccb_r_phase(pData, ack);
		read_data <<= 8;
		read_data |= temp;
	}

	*data = read_data;

__exit:
	// Transmission stop
	sccb_stop(pData);
	return ret;
}

static void sccb_init(sccb_config_t *pSccb)
{
	gpio_set_port_attribute(pSccb->scl_port, ATTRIBUTE_HIGH);
	gpio_set_port_attribute(pSccb->sda_port, ATTRIBUTE_HIGH);
	gpio_init_io(pSccb->scl_port, GPIO_OUTPUT);			//set dir
	gpio_init_io(pSccb->sda_port, GPIO_OUTPUT);			//set dir
	gpio_write_io(pSccb->scl_port, DATA_HIGH);			//SCL
	gpio_write_io(pSccb->sda_port, DATA_HIGH);			//SDA

	if(pSccb->have_pwdn)
	{
		gpio_set_port_attribute(pSccb->pwdn_port, ATTRIBUTE_HIGH);
		gpio_init_io(pSccb->pwdn_port, GPIO_OUTPUT);	//set dir
		gpio_write_io(pSccb->pwdn_port, DATA_LOW);		//SDA0
	}

	gpio_drving_init_io(pSccb->scl_port, pSccb->scl_drv);
	gpio_drving_init_io(pSccb->sda_port, pSccb->sda_drv);

	#if 0
	// wait io stable
	sccb_delay(200);
	#else
	sccb_delay(1);
	#endif

	return;
}

/**
 * @brief   sccb write data function
 * @param   addr[in] : register address
 * @param   data[in] : write data
 * @return 	STATUS_OK / STATUS_FAIL
 */
INT32S drv_l2_sccb_write(sccb_config_t* pSccb, INT16U addr, INT16U data)
{
	INT32S ret;

	sccb_lock(pSccb);

	sccb_init(pSccb);

	ret = sccb_write(pSccb, addr, data);

	sccb_unlock(pSccb);

	return ret;
}

/**
 * @brief   sccb read data function
 * @param   addr[in] : register address
 * @param   data[out] : read data
 * @return 	STATUS_OK / STATUS_FAIL
 */
INT32S drv_l2_sccb_read(sccb_config_t* pSccb, INT16U addr, INT16U* data)
{
	INT32S ret;

	sccb_lock(pSccb);

	sccb_init(pSccb);

	ret = sccb_read(pSccb, addr, data);
	if(ret < 0)
	{
		*data = 0xFF;
	}

	sccb_unlock(pSccb);

	return ret;
}

#endif //(defined _DRV_L2_SCCB) && (_DRV_L2_SCCB == 1)
